package com.lollipop.synthential.algorithm;

import lombok.extern.slf4j.Slf4j;

import java.util.HashMap;
import java.util.Map;

/**
 * @Author: J.K
 * @Date: 2022-07-29 14:48
 * @Description: 平滑加权轮询算法
 *
 * 在该算法中，会先计算已配置的权重值总和，然后第一次请求，会初始化权重容器 weightMap，将每个配置的节点都封装成一个 Weight 对象，并将其动态权重值初始化为 0。
 * Weight("server":"44.120.110.001:8080","weight":3,"currentWeight":0)
 * Weight("server":"44.120.110.002:8081","weight":2,"currentWeight":0)
 * Weight("server":"44.120.110.003:8082","weight":1,"currentWeight":0)
 *
 * 用原本的动态权重值加一次每个节点的静态权重值，计算出新的动态权重值。
 * 遍历权重容器，找出动态权重值最大的节点，将其作为处理本次请求的节点。
 * 用最大的动态权重值减去已配置的静态权重值总和，为一下轮分发做准备。
 */
@Slf4j
public class SmoothRoundRibbonWeight {
    /**
     * 初始化存储每个节点的权重容器
     */
    private static Map<String, Weight> weightMap = new HashMap<>();

    /**
     * 计算总权重值，只需要计算一次，因此放在静态代码块中执行
     */
    private static int weightTotal = 0;

    static {
        sumWeightTotal();
    }

    /**
     * 求和总权重值，后续动态伸缩节点时，再次调用该方法即可
     */
    public static void sumWeightTotal() {
        for (Integer weight : WeightServers.WEIGHT_SERVERS.values()) {
            weightTotal += weight;
        }
    }

    /**
     * 获取处理本次请求的具体服务器IP
     *
     * @return
     */
    public static String getServer() {
        // 判断权重容器中是否有节点信息
        if (weightMap.isEmpty()) {
            // 如果没有则将配置的权重服务器列表挨个载入容器
            WeightServers.WEIGHT_SERVERS.forEach((servers, weight) -> {
                // 初始化时，每个节点的动态权重值都为0
                weightMap.put(servers, new Weight(servers, weight, 0));
            });
        }

        // 每次请求时，更改动态权重值
        for (Weight weight : weightMap.values()) {
            weight.setCurrentWeight(weight.getCurrentWeight()
                    + weight.getWeight());
        }

        // 判断权重容器中最大的动态权重值
        Weight maxCurrentWeight = null;
        for (Weight weight : weightMap.values()) {
            if (maxCurrentWeight == null || weight.getCurrentWeight()
                    > maxCurrentWeight.getCurrentWeight()) {
                maxCurrentWeight = weight;
            }
        }

        // 最后用最大的动态权重值减去所有节点的总权重值
        maxCurrentWeight.setCurrentWeight(maxCurrentWeight.getCurrentWeight()
                - weightTotal);

        // 返回最大的动态权重值对应的节点IP
        return maxCurrentWeight.getServer();
    }

    public static void main(String[] args) {
        // 模拟6次请求
        for (int i = 1; i <= 6; i++) {
            log.info("第[{}]个请求，服务节点：[{}]", i, RandomRibbon.getServer());
        }
    }
}
